package org.budo.phone.location.service.fengjiajie;

import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;

import org.budo.phone.location.service.api.AbstractBudoPhoneLocation;
import org.budo.phone.location.service.api.BudoPhoneLocation;
import org.budo.phone.location.service.api.BudoPhoneLocationService;
import org.budo.support.lang.util.IoUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class FengjiajiePhoneLocationService implements BudoPhoneLocationService {
    private static final String[] PHONE_NUMBER_TYPE = { null, "移动", "联通", "电信", "电信虚拟运营商", "联通虚拟运营商", "移动虚拟运营商" };

    private static final String PHONE_DAT_FILE_PATH = "phone-location-fengjiajie.data";

    private static final int INDEX_SEGMENT_LENGTH = 9;

    /**
     * 索引的数量
     */
    private int phoneRecordCount;

    /**
     * 索引区的偏移
     */
    private int indexAreaOffset;

    /**
     * 号码格式的字节数组
     */
    private byte[] dataByteArray;

    /**
     * 记录文件的字节缓存区
     */
    private ByteBuffer byteBuffer;

    /**
     * 记录文件的版本
     */
    private int version;

    /**
     * 初始化数据
     */
    private synchronized void assertInited() {
        if (null != this.dataByteArray) {
            return;
        }

        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(PHONE_DAT_FILE_PATH);
        this.dataByteArray = IoUtil.toByteArray(inputStream);

        ByteBuffer byteBuffer = ByteBuffer.wrap(this.dataByteArray);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

        this.byteBuffer = byteBuffer;
        this.version = byteBuffer.getInt();
        this.indexAreaOffset = byteBuffer.getInt();
        this.phoneRecordCount = (this.dataByteArray.length - this.indexAreaOffset) / INDEX_SEGMENT_LENGTH;

        log.info("#70 init, this=" + this + ", version=" + this.version + ", phoneRecordCount=" + this.phoneRecordCount);
    }

    public BudoPhoneLocation seekPhoneLocation(String phone) {
        this.assertInited(); // 初始化

        if (null == phone || phone.length() < 7) {
            log.error("#19 phone=" + phone + ", not ok");
            return null;
        }

        Integer prefix = Integer.valueOf(phone.substring(0, 7));

        try {
            return this.seek(phone, prefix);
        } catch (Throwable e) {
            log.error("#89 phone=" + phone + ", this=" + this + ", e=" + e, e);
            return null;
        }
    }

    private BudoPhoneLocation seek(String phone, Integer prefix) {
        // 使用二分查找
        int left = 0;
        int right = this.phoneRecordCount;
        while (left <= right) {
            int middle = (left + right) >> 1; // 表示除以2 速度更快
            int currentOffset = this.indexAreaOffset + middle * INDEX_SEGMENT_LENGTH;
            if (currentOffset >= this.dataByteArray.length) {
                return null;
            }

            this.byteBuffer.position(currentOffset);
            int currentPrefix = this.byteBuffer.getInt();
            if (currentPrefix > prefix) {
                right = middle - 1;
            } else if (currentPrefix < prefix) {
                left = middle + 1;
            } else {
                int infoBeginOffset = this.byteBuffer.getInt();
                int phoneType = this.byteBuffer.get();

                int infoLength = -1;
                for (int i = infoBeginOffset; i < this.indexAreaOffset; ++i) {
                    if (this.dataByteArray[i] == 0) {
                        infoLength = i - infoBeginOffset;
                        break;
                    }
                }

                String infoString = new String(this.dataByteArray, infoBeginOffset, infoLength, StandardCharsets.UTF_8);
                AbstractBudoPhoneLocation phoneLocation = this.toPhoneLocation(infoString);

                phoneLocation.setServiceProvider(PHONE_NUMBER_TYPE[phoneType]);
                return phoneLocation;
            }
        }

        log.warn("#94 return null, phone=" + phone + ", this=" + this);
        return null;
    }

    private AbstractBudoPhoneLocation toPhoneLocation(String phoneInfo) {
        String[] infoSegments = phoneInfo.split("\\|");

        AbstractBudoPhoneLocation phoneNumberInfo = new AbstractBudoPhoneLocation();
        phoneNumberInfo.setProvince(infoSegments[0]);
        phoneNumberInfo.setCity(infoSegments[1]);
        // setZipCode(infoSegments[2]);
        // setAreaCode(infoSegments[3]);

        return phoneNumberInfo;
    }

}